/*
Copyright 2011 Johan Maasing

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package nu.zoom.catonine.swing.tail.gutter;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;

import nu.zoom.catonine.swing.tail.FoldedDocumentLogEntry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author "Johan Maasing" &lt;johan@zoom.nu&gt;
 * 
 */
public class GutterView extends JComponent {
	private static final long serialVersionUID = 6189389750396872064L;
	private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
	private static final int GUTTER_XINSET = 2;
	private final Log log = LogFactory.getLog(getClass());
	private final GutterDocument document;
	private final JTextPane textArea;
	private final Image foldedIcon;
	private final Image unfoldedIcon;
	private final int iconWidth;
	private CachedValues cv;
	private int lastNumberOfDigitsInGutter = 1;

	public GutterView(final JTextPane textArea, final GutterDocument document,
			final Image foldedIcon, final Image unfoldedIcon,
			final int iconWidth) {
		super();
		this.document = document;
		this.textArea = textArea;
		this.foldedIcon = foldedIcon;
		this.unfoldedIcon = unfoldedIcon;
		this.iconWidth = iconWidth;
		this.addMouseListener(new MouseAdapter());
	}

	@Override
	protected void paintComponent(final Graphics gr) {
		super.paintComponent(gr);
		boolean cacheNeedsUpdateNextRepaint = false;
		updateCachedValues();

		Graphics g = gr.create();
		final Rectangle clipBounds = g.getClipBounds();
		g.setFont(this.cv.gutterFont);
		FontMetrics fontMetrics = this.getFontMetrics(this.cv.gutterFont);

		try {
			List<OffsetCollection> offsets = calculateOffsets(clipBounds);
			for (OffsetCollection offset : offsets) {
				String text = Long.toString(offset.filePosition);
				if (this.lastNumberOfDigitsInGutter < text.length()) {
					this.lastNumberOfDigitsInGutter = text.length();
					cacheNeedsUpdateNextRepaint = true;
				}
				final int stringXOffset = this.cv.gutterWidth
						- fontMetrics.stringWidth(text) - GUTTER_XINSET;
				g.setColor(Color.BLACK);
				g.drawLine(clipBounds.x, clipBounds.y + offset.y, clipBounds.x
						+ clipBounds.width, clipBounds.y + offset.y);
				g.drawString(text, clipBounds.x + stringXOffset, clipBounds.y
						+ offset.y + this.cv.baseline);
				if (!offset.unfoldable) {
					// I don't like the folding implementation, hide it until I can refactor
					// the rendering and not use Swing text components.
					/*
					final Image foldImage = (offset.folded) ? this.foldedIcon
							: this.unfoldedIcon;
					g.drawImage(foldImage, clipBounds.x + GUTTER_XINSET,
							clipBounds.y + offset.y + 1, null);
							*/
				}
			}
		} catch (BadLocationException e) {
			log.error(e);
		}
		if (cacheNeedsUpdateNextRepaint) {
			this.cv = null;
		}
	}

	private List<OffsetCollection> calculateOffsets(final Rectangle clipBounds)
			throws BadLocationException {
		final LinkedList<OffsetCollection> offsets = new LinkedList<GutterView.OffsetCollection>();

		final Point viewStartPoint = new Point(0, clipBounds.y);
		final Point viewEndPoint = new Point(clipBounds.width, clipBounds.y
				+ clipBounds.height);
		int modelStartOffset = this.textArea.viewToModel(viewStartPoint);
		int modelEndOffset = this.textArea.viewToModel(viewEndPoint);

		final List<FoldedDocumentLogEntry> intersectingLogEntries = this.document
				.getIntersectingLogEntries(modelStartOffset, modelEndOffset);

		for (final FoldedDocumentLogEntry documentLogEntry : intersectingLogEntries) {
			if (!documentLogEntry.synthetic) {
				final Rectangle entryViewPosition = this.textArea
						.modelToView(documentLogEntry.documentOffset);
				final OffsetCollection oc = new OffsetCollection();
				oc.filePosition = documentLogEntry.fileOffset;
				oc.documentOffset = documentLogEntry.documentOffset;
				oc.y = entryViewPosition.y - clipBounds.y;
				oc.folded = documentLogEntry.folded;
				oc.unfoldable = documentLogEntry.unfoldable;
				OffsetCollection last = offsets.peekLast();
				if (last != null) {
					last.height = oc.y - last.y;
					if (last.y == oc.y) {
						log.error("Overlapping entries");
					}
				}
				offsets.add(oc);
			}
		}
		OffsetCollection last = offsets.peekLast();
		if (last != null) {
			last.height = clipBounds.height - last.y;
		}

		return offsets;
	}

	public void restyle() {
		this.cv = null;
	}

	static class OffsetCollection {
		boolean folded = false;
		boolean unfoldable = true;
		long filePosition;
		int documentOffset;
		int height;
		int y;
	}

	private void updateCachedValues() {
		if (this.cv == null) {
			CachedValues cv = new CachedValues();
			Font textAreaFont = (document.getDefaultFont() == null) ? textArea
					.getFont() : document.getDefaultFont();
			FontMetrics fontMetrics = textArea.getFontMetrics(textAreaFont);
			cv.fontHeight = fontMetrics.getHeight();
			cv.baseline = cv.fontHeight - fontMetrics.getDescent();
			// Make gutter font smaller but no less than 8 points
			int gutterFontHeight = Math.max((cv.fontHeight - 4), 8);
			cv.gutterFont = new Font(Font.SANS_SERIF, Font.PLAIN,
					gutterFontHeight);

			FontMetrics gutterFontMetrics = getFontMetrics(cv.gutterFont);
			cv.gutterWidth = gutterFontMetrics.charWidth('0')
					* this.lastNumberOfDigitsInGutter + 2 * GUTTER_XINSET
					+ this.iconWidth;

			Dimension d = new Dimension(cv.gutterWidth, HEIGHT);
			setPreferredSize(d);
			setSize(d);

			this.cv = cv;
		}
	}

	static class CachedValues {
		Font gutterFont;
		int baseline;
		int lastHeight;
		int gutterWidth;
		int fontHeight;
	}

	class MouseAdapter extends java.awt.event.MouseAdapter {

		@Override
		public void mouseClicked(MouseEvent evnt) {
			if (evnt.getButton() == MouseEvent.BUTTON1) {
				Point position = evnt.getPoint();
				position.x = 0;
				/*
				final int documentOffset = textArea.viewToModel(position);
				try {
					document.toggleFold(documentOffset);
				} catch (BadLocationException e) {
					log.error(e);
				}
				invalidate();
				*/
			}
		}

	}
}
